import 'package:math_keyboard/src/foundation/node.dart';
import 'package:math_keyboard/src/foundation/extended_nodes.dart';

/// Direction for matrix navigation.
enum NavigationDirection {
  up,
  down,
  left,
  right,
}

/// Editor for managing matrix operations and navigation.
class MatrixEditor {
  /// Creates a matrix with the specified dimensions.
  static TeXMatrix createMatrix(
    int rows, 
    int cols, {
    TeXNode? parent,
    String matrixType = 'pmatrix',
  }) {
    if (rows <= 0 || cols <= 0) {
      throw ArgumentError('Matrix dimensions must be positive');
    }
    
    return TeXMatrix(
      parent ?? TeXNode(null),
      rows,
      cols,
      matrixType: matrixType,
    );
  }

  /// Navigates within a matrix based on the current position and direction.
  static MatrixNavigationResult navigateMatrix(
    TeXMatrix matrix,
    int currentRow,
    int currentCol,
    NavigationDirection direction,
  ) {
    if (currentRow < 0 || currentRow >= matrix.rows ||
        currentCol < 0 || currentCol >= matrix.columns) {
      throw ArgumentError('Current position ($currentRow, $currentCol) is out of bounds');
    }

    int newRow = currentRow;
    int newCol = currentCol;

    switch (direction) {
      case NavigationDirection.up:
        newRow = currentRow - 1;
        break;
      case NavigationDirection.down:
        newRow = currentRow + 1;
        break;
      case NavigationDirection.left:
        newCol = currentCol - 1;
        break;
      case NavigationDirection.right:
        newCol = currentCol + 1;
        break;
    }

    // Check if the new position is within bounds
    if (newRow >= 0 && newRow < matrix.rows &&
        newCol >= 0 && newCol < matrix.columns) {
      return MatrixNavigationResult(
        success: true,
        newRow: newRow,
        newCol: newCol,
        targetCell: matrix.getCell(newRow, newCol),
      );
    } else {
      return MatrixNavigationResult(
        success: false,
        newRow: currentRow,
        newCol: currentCol,
        targetCell: matrix.getCell(currentRow, currentCol),
        exitDirection: direction,
      );
    }
  }

  /// Gets the current position of a cell within the matrix.
  static MatrixPosition? getCellPosition(TeXMatrix matrix, TeXNode cell) {
    for (int row = 0; row < matrix.rows; row++) {
      for (int col = 0; col < matrix.columns; col++) {
        if (matrix.getCell(row, col) == cell) {
          return MatrixPosition(row: row, col: col);
        }
      }
    }
    return null;
  }

  /// Inserts a new row at the specified position.
  static TeXMatrix insertRow(TeXMatrix matrix, int position) {
    if (position < 0 || position > matrix.rows) {
      throw ArgumentError('Row position $position is out of bounds');
    }

    // Create a new matrix with one additional row
    final newMatrix = TeXMatrix(
      matrix.parent,
      matrix.rows + 1,
      matrix.columns,
      matrixType: matrix.matrixType,
    );

    // Copy existing cells, inserting empty row at the specified position
    int sourceRow = 0;
    for (int row = 0; row < newMatrix.rows; row++) {
      if (row == position) {
        // Insert empty row
        for (int col = 0; col < newMatrix.columns; col++) {
          newMatrix.setCell(row, col, TeXNode(newMatrix));
        }
      } else {
        // Copy from original matrix
        for (int col = 0; col < newMatrix.columns; col++) {
          final sourceCell = matrix.getCell(sourceRow, col);
          newMatrix.setCell(row, col, sourceCell);
          sourceCell.parent = newMatrix;
        }
        sourceRow++;
      }
    }

    return newMatrix;
  }

  /// Inserts a new column at the specified position.
  static TeXMatrix insertColumn(TeXMatrix matrix, int position) {
    if (position < 0 || position > matrix.columns) {
      throw ArgumentError('Column position $position is out of bounds');
    }

    // Create a new matrix with one additional column
    final newMatrix = TeXMatrix(
      matrix.parent,
      matrix.rows,
      matrix.columns + 1,
      matrixType: matrix.matrixType,
    );

    // Copy existing cells, inserting empty column at the specified position
    for (int row = 0; row < newMatrix.rows; row++) {
      int sourceCol = 0;
      for (int col = 0; col < newMatrix.columns; col++) {
        if (col == position) {
          // Insert empty cell
          newMatrix.setCell(row, col, TeXNode(newMatrix));
        } else {
          // Copy from original matrix
          final sourceCell = matrix.getCell(row, sourceCol);
          newMatrix.setCell(row, col, sourceCell);
          sourceCell.parent = newMatrix;
          sourceCol++;
        }
      }
    }

    return newMatrix;
  }

  /// Removes a row at the specified position.
  static TeXMatrix? removeRow(TeXMatrix matrix, int position) {
    if (position < 0 || position >= matrix.rows) {
      throw ArgumentError('Row position $position is out of bounds');
    }

    if (matrix.rows <= 1) {
      // Cannot remove the last row
      return null;
    }

    // Create a new matrix with one fewer row
    final newMatrix = TeXMatrix(
      matrix.parent,
      matrix.rows - 1,
      matrix.columns,
      matrixType: matrix.matrixType,
    );

    // Copy existing cells, skipping the specified row
    int targetRow = 0;
    for (int row = 0; row < matrix.rows; row++) {
      if (row != position) {
        for (int col = 0; col < matrix.columns; col++) {
          final sourceCell = matrix.getCell(row, col);
          newMatrix.setCell(targetRow, col, sourceCell);
          sourceCell.parent = newMatrix;
        }
        targetRow++;
      }
    }

    return newMatrix;
  }

  /// Removes a column at the specified position.
  static TeXMatrix? removeColumn(TeXMatrix matrix, int position) {
    if (position < 0 || position >= matrix.columns) {
      throw ArgumentError('Column position $position is out of bounds');
    }

    if (matrix.columns <= 1) {
      // Cannot remove the last column
      return null;
    }

    // Create a new matrix with one fewer column
    final newMatrix = TeXMatrix(
      matrix.parent,
      matrix.rows,
      matrix.columns - 1,
      matrixType: matrix.matrixType,
    );

    // Copy existing cells, skipping the specified column
    for (int row = 0; row < matrix.rows; row++) {
      int targetCol = 0;
      for (int col = 0; col < matrix.columns; col++) {
        if (col != position) {
          final sourceCell = matrix.getCell(row, col);
          newMatrix.setCell(row, targetCol, sourceCell);
          sourceCell.parent = newMatrix;
          targetCol++;
        }
      }
    }

    return newMatrix;
  }

  /// Clears all content from the matrix cells.
  static void clearMatrix(TeXMatrix matrix) {
    for (int row = 0; row < matrix.rows; row++) {
      for (int col = 0; col < matrix.columns; col++) {
        final cell = matrix.getCell(row, col);
        cell.children.clear();
        cell.courserPosition = 0;
      }
    }
  }

  /// Fills the matrix with a pattern (useful for testing).
  static void fillMatrixWithPattern(TeXMatrix matrix, String Function(int row, int col) pattern) {
    for (int row = 0; row < matrix.rows; row++) {
      for (int col = 0; col < matrix.columns; col++) {
        final cell = matrix.getCell(row, col);
        cell.children.clear();
        cell.children.add(TeXLeaf(pattern(row, col)));
        cell.courserPosition = 1;
      }
    }
  }
}

/// Result of a matrix navigation operation.
class MatrixNavigationResult {
  /// Creates a [MatrixNavigationResult].
  const MatrixNavigationResult({
    required this.success,
    required this.newRow,
    required this.newCol,
    required this.targetCell,
    this.exitDirection,
  });

  /// Whether the navigation was successful.
  final bool success;

  /// The new row position after navigation.
  final int newRow;

  /// The new column position after navigation.
  final int newCol;

  /// The target cell after navigation.
  final TeXNode targetCell;

  /// The direction of exit if navigation failed (reached matrix boundary).
  final NavigationDirection? exitDirection;
}

/// Position within a matrix.
class MatrixPosition {
  /// Creates a [MatrixPosition].
  const MatrixPosition({
    required this.row,
    required this.col,
  });

  /// The row index.
  final int row;

  /// The column index.
  final int col;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is MatrixPosition &&
          runtimeType == other.runtimeType &&
          row == other.row &&
          col == other.col;

  @override
  int get hashCode => row.hashCode ^ col.hashCode;

  @override
  String toString() => 'MatrixPosition(row: $row, col: $col)';
}